package manager

import (
	"fmt"
	"strings"

	"github.com/Kindling-project/chaos-backend/api"
	"github.com/Kindling-project/chaos-backend/tools/kubernetescli"
	cli "github.com/Kindling-project/chaos-backend/tools/kubernetescli"
	"github.com/hashicorp/go-multierror"
)

var DefaultManager *ChaosManager
var UnstructuredCli *BuildInChaos

func init() {
	DefaultManager = &ChaosManager{
		BuildInChaosMap: make(BuildInChaosMap),
	}

	DefaultManager.RegisterBuiltInChaos(&cli.NetworkChaosCli, cli.NetworkChaosType, []string{"*"})
	DefaultManager.RegisterBuiltInChaos(&cli.StressChaosCli, cli.StressChaosType, []string{"*"})
	DefaultManager.RegisterBuiltInChaos(&cli.JVMChaosCli, cli.JvmChaosType, []string{"*"})
	DefaultManager.RegisterBuiltInChaos(&cli.IOChaosCli, cli.IoChaosType, []string{"*"})
	DefaultManager.RegisterBuiltInChaos(&cli.HTTPChaosCli, cli.HTTPChaosType, []string{"*"})
}

type ChaosManager struct {
	BuildInChaosMap
}

func (cm *ChaosManager) ListChaos() ([]api.Chaos, error) {
	var result = make([]api.Chaos, 0)
	var multiErr *multierror.Error
	for _, buildInChaos := range cm.BuildInChaosMap {
		cli := buildInChaos.ChaosCli
		if list, err := cli.List(); err == nil {
			// list
			chaos := cli.ToChaosVO(&list)
			result = append(result, chaos...)
		} else {
			multierror.Append(multiErr, err)
		}
	}
	return result, multiErr.ErrorOrNil()
}

func (cm *ChaosManager) CreateChaos(vo *api.Chaos) (*api.Chaos, error) {
	buildInChaos := cm.BuildInChaosMap.GetBuildChaosInfo(vo)
	cli := buildInChaos.ChaosCli
	if obj, err := cli.Create(vo); err != nil {
		return nil, err
	} else {
		return &cli.ToChaosVO(&obj)[0], nil
	}
}

func (cm *ChaosManager) DeleteChaos() (api.Chaos, error) {
	var lastChaos api.Chaos
	var multiErr *multierror.Error
	for _, buildInChaos := range cm.BuildInChaosMap {
		cli := buildInChaos.ChaosCli
		if chaosDeleted, err := cli.DeleteAll(); err != nil {
			multierror.Append(multiErr, err)
		} else {
			if chaosInfo := (cli.ToChaosVO(&chaosDeleted)); len(chaosInfo) > 0 {
				lastChaos = chaosInfo[0]
			}
		}
	}
	return lastChaos, multiErr.ErrorOrNil()
}

func (cm *ChaosManager) Status() (*api.Chaos, error) {
	var lastChaos *api.Chaos
	var multiErr *multierror.Error
	for _, buildInChaos := range cm.BuildInChaosMap {
		cli := buildInChaos.ChaosCli
		if chaos, err := cli.List(); err != nil {
			multierror.Append(multiErr, err)
		} else {
			for _, v := range cli.ToChaosVO(&chaos) {
				// TODO filter pause
				lastChaos = &v
			}
		}
	}

	return lastChaos, multiErr.ErrorOrNil()
}

func ListChaos() ([]api.Chaos, error) {
	return DefaultManager.ListChaos()
}

func CreateChaos(vo *api.Chaos) (*api.Chaos, error) {
	return DefaultManager.CreateChaos(vo)
}

func DeleteChaos() (api.Chaos, error) {
	return DefaultManager.DeleteChaos()
}

func Status() (*api.Chaos, error) {
	return DefaultManager.Status()
}

type BuildInChaos struct {
	ChaosCli kubernetescli.ChaosCli
	Action   string
}
type BuildInChaosMap map[string]*BuildInChaos

func (m BuildInChaosMap) RegisterBuiltInChaos(client kubernetescli.ChaosCli, chaosType string, actions []string) {
	for _, action := range actions {
		clientId := fmt.Sprintf("%s#%s", chaosType, action)
		m[clientId] = &BuildInChaos{ChaosCli: client, Action: action}
	}
}

func (m BuildInChaosMap) RegisterUnstructuredChaos(client kubernetescli.ChaosCli, complexAction string) {
	m[complexAction] = &BuildInChaos{ChaosCli: client, Action: Unstructured}
}

func (m BuildInChaosMap) GetBuildChaosInfo(vo *api.Chaos) *BuildInChaos {
	chaosType, chaosAction, options := analysisChaosTypeAndChaosActionFromTemplate(vo.ComplexAction)
	// 完善参数
	vo.ChaosType = chaosType
	vo.ChaosAction = chaosAction
	if options != NoOption {
		vo.Params = append(vo.Params, api.ChaosParams{
			Name:  "option",
			Type:  "string",
			Value: options,
		})
	}
	if chaosAction == Unstructured {
		return m[vo.ComplexAction]
	}
	clientId := fmt.Sprintf("%s#%s", chaosType, chaosAction)
	if res, find := m[clientId]; find {
		return res
	}

	clientId = fmt.Sprintf("%s#%s", chaosType, "*")
	return m[clientId]
}

func analysisChaosTypeAndChaosActionFromTemplate(complexAction string) (chaosType string, action string, options string) {
	parts := strings.Split(complexAction, "#")
	if len(parts) < 2 {
		return complexAction, NoAction, NoOption
	} else if len(parts) < 3 {
		return parts[0], parts[1], NoAction
	} else {
		return parts[0], parts[1], strings.Join(parts[2:], "#")
	}
}
